{-# LANGUAGE NamedFieldPuns #-}

module Deftype
  ( moduleForDeftype,
    moduleForDeftypeInContext,
    bindingsForRegisteredType,
    fieldArg,
    memberArg,
  )
where

import Concretize
import Context
import Data.Maybe
import Env (addListOfBindings, new)
import Info
import Managed
import Obj
import StructUtils
import Template
import TemplateGenerator as TG
import ToTemplate
import qualified TypeCandidate as TC
import TypeError
import TypePredicates
import Types
import TypesToC
import Util
import Validate

{-# ANN module "HLint: ignore Reduce duplication" #-}

moduleForDeftypeInContext :: Context -> String -> [Ty] -> [XObj] -> Maybe Info -> Either TypeError (String, XObj, [XObj])
moduleForDeftypeInContext ctx name vars members info =
  let global = contextGlobalEnv ctx
      types = contextTypeEnv ctx
      path = contextPath ctx
      inner = either (const Nothing) Just (innermostModuleEnv ctx)
      previous =
        either
          (const Nothing)
          Just
          ( (lookupBinderInInternalEnv ctx (SymPath path name))
              <> (lookupBinderInGlobalEnv ctx (SymPath path name))
                >>= \b ->
                  replaceLeft
                    (NotFoundGlobal (SymPath path name))
                    ( case binderXObj b of
                        XObj (Mod ev et) _ _ -> Right (ev, et)
                        _ -> Left "Non module"
                    )
          )
   in moduleForDeftype inner types global path name vars members info previous

-- | This function creates a "Type Module" with the same name as the type being defined.
--   A type module provides a namespace for all the functions that area automatically
--   generated by a deftype.
moduleForDeftype :: Maybe Env -> TypeEnv -> Env -> [String] -> String -> [Ty] -> [XObj] -> Maybe Info -> Maybe (Env, TypeEnv) -> Either TypeError (String, XObj, [XObj])
moduleForDeftype innerEnv typeEnv env pathStrings typeName typeVariables rest i existingEnv =
  let moduleValueEnv = fromMaybe (new innerEnv (Just typeName)) (fmap fst existingEnv)
      moduleTypeEnv = fromMaybe (new (Just typeEnv) (Just typeName)) (fmap snd existingEnv)
      initmembers = case rest of
        -- ANSI C does not allow empty structs. We add a dummy member here to account for this.
        -- Note that we *don't* add this member for external types--we leave those definitions up to the user.
        -- The corresponding field is emitted for the struct definition in Emit.hs
        [(XObj (Arr []) ii t)] -> [(XObj (Arr [(XObj (Sym (SymPath [] "__dummy") Symbol) Nothing Nothing), (XObj (Sym (SymPath [] "Char") Symbol) Nothing Nothing)]) ii t)]
        _ -> rest
   in do
        let mems = case initmembers of
              [(XObj (Arr ms) _ _)] -> ms
              _ -> []
        -- Check that this is a valid type definition.
        candidate <- TC.mkStructCandidate typeName typeVariables typeEnv env mems pathStrings
        validateType candidate
        -- Generate standard function bindings for the type.
        (funcs, deps) <- generateTypeBindings candidate
        -- Add the type and bindings to the environment.
        let moduleEnvWithBindings = addListOfBindings moduleValueEnv funcs
            typeModuleXObj = XObj (Mod moduleEnvWithBindings moduleTypeEnv) i (Just ModuleTy)
        pure (typeName, typeModuleXObj, deps)

-- | Will generate getters/setters/updaters when registering EXTERNAL types.
-- | i.e. (register-type VRUnicornData [hp Int, magic Float])
-- | TODO: Remove duplication shared by moduleForDeftype-function.
bindingsForRegisteredType :: TypeEnv -> Env -> [String] -> String -> [XObj] -> Maybe Info -> Maybe (Env, TypeEnv) -> Either TypeError (String, XObj, [XObj])
bindingsForRegisteredType typeEnv env pathStrings typeName rest i existingEnv =
  let moduleValueEnv = fromMaybe (new (Just env) (Just typeName)) (fmap fst existingEnv)
      moduleTypeEnv = fromMaybe (new (Just typeEnv) (Just typeName)) (fmap snd existingEnv)
   in do
        let mems = case rest of
              [(XObj (Arr ms) _ _)] -> ms
              _ -> []
        -- Check that this is a valid type definition.
        candidate <- TC.mkStructCandidate typeName [] typeEnv env mems pathStrings
        validateType candidate
        -- Generate function bindings for the registered type.
        (binders, deps) <- templatesForMembers candidate
        okInit <- binderForInit candidate
        (okStr, strDeps) <- binderForStrOrPrn "str" candidate
        (okPrn, _) <- binderForStrOrPrn "prn" candidate
        -- Add the type and bindings to the environment.
        let moduleEnvWithBindings = addListOfBindings moduleValueEnv (okInit : okStr : okPrn : binders)
            typeModuleXObj = XObj (Mod moduleEnvWithBindings moduleTypeEnv) i (Just ModuleTy)
        pure (typeName, typeModuleXObj, deps ++ strDeps)

--------------------------------------------------------------------------------
-- Binding creators

-- | Generate the standard set of functions for a new type.
generateTypeBindings :: TC.TypeCandidate -> Either TypeError ([(String, Binder)], [XObj])
generateTypeBindings candidate =
  do
    (okMembers, membersDeps) <- templatesForMembers candidate
    okInit <- binderForInit candidate
    (okStr, strDeps) <- binderForStrOrPrn "str" candidate
    (okPrn, _) <- binderForStrOrPrn "prn" candidate
    (okDelete, deleteDeps) <- binderForDelete candidate
    (okCopy, copyDeps) <- binderForCopy candidate
    pure
      ( (okInit : okStr : okPrn : okDelete : okCopy : okMembers),
        (deleteDeps ++ membersDeps ++ copyDeps ++ strDeps)
      )

-- | Generate all the templates for ALL the member variables in a deftype declaration.
templatesForMembers :: TC.TypeCandidate -> Either TypeError ([(String, Binder)], [XObj])
templatesForMembers candidate =
  let bindersAndDeps = concatMap (templatesForSingleMember candidate) (TC.getFields candidate)
   in Right (map fst bindersAndDeps, concatMap snd bindersAndDeps)

-- | Generate the templates for a single member in a deftype declaration.
templatesForSingleMember :: TC.TypeCandidate -> TC.TypeField -> [((String, Binder), [XObj])]
templatesForSingleMember _ (TC.StructField "__dummy" _) = []
templatesForSingleMember candidate field@(TC.StructField _ t) =
  case t of
    -- Unit member types are special since we do not represent them in emitted c.
    -- Instead, members of type Unit are executed for their side effects and silently omitted
    -- from the produced C structs.
    UnitTy ->
      binders
        (FuncTy [RefTy p (VarTy "q")] UnitTy StaticLifetimeTy)
        (FuncTy [p, t] p StaticLifetimeTy)
        (FuncTy [RefTy p (VarTy "q"), t] UnitTy StaticLifetimeTy)
        (FuncTy [p, RefTy (FuncTy [] UnitTy (VarTy "fq")) (VarTy "q")] p StaticLifetimeTy)
    _ ->
      binders
        (FuncTy [RefTy p (VarTy "q")] (RefTy t (VarTy "q")) StaticLifetimeTy)
        (FuncTy [p, t] p StaticLifetimeTy)
        (FuncTy [RefTy p (VarTy "q"), t] UnitTy StaticLifetimeTy)
        (FuncTy [p, RefTy (FuncTy [t] t (VarTy "fq")) (VarTy "q")] p StaticLifetimeTy)
  where
    p = TC.toType candidate
    memberName = TC.fieldName field
    binders getterSig setterSig mutatorSig updaterSig =
      [ getter getterSig,
        setter setterSig,
        mutator mutatorSig,
        updater updaterSig
      ]

    getter :: Ty -> ((String, Binder), [XObj])
    getter sig =
      let doc = "gets the `" ++ (TC.fieldName field) ++ "` property of a `" ++ (TC.getName candidate) ++ "`."
          binderT = sig
          binderP = SymPath (TC.getFullPath candidate) (TC.fieldName field)
          temp = TG.generateConcreteFieldTemplate candidate field getterGenerator
       in instanceBinderWithDeps binderP binderT temp doc

    setter :: Ty -> ((String, Binder), [XObj])
    setter sig =
      let doc = "sets the `" ++ (TC.fieldName field) ++ "` property of a `" ++ (TC.getName candidate) ++ "`."
          binderT = sig
          binderP = SymPath (TC.getFullPath candidate) ("set-" ++ (TC.fieldName field))
          concrete = (TG.generateConcreteFieldTemplate candidate field setterGenerator)
          generic = (TG.generateGenericFieldTemplate candidate field setterGenerator)
       in if isTypeGeneric t
            then (defineTypeParameterizedTemplate generic binderP binderT doc, [])
            else instanceBinderWithDeps binderP binderT concrete doc

    mutator :: Ty -> ((String, Binder), [XObj])
    mutator sig =
      let doc = "sets the `" ++ (TC.fieldName field) ++ "` property of a `" ++ (TC.getName candidate) ++ "` in place."
          binderT = sig
          binderP = SymPath (TC.getFullPath candidate) ("set-" ++ (TC.fieldName field) ++ "!")
          concrete = (TG.generateConcreteFieldTemplate candidate field mutatorGenerator)
          generic = (TG.generateGenericFieldTemplate candidate field mutatorGenerator)
       in if isTypeGeneric t
            then (defineTypeParameterizedTemplate generic binderP binderT doc, [])
            else instanceBinderWithDeps binderP binderT concrete doc

    updater :: Ty -> ((String, Binder), [XObj])
    updater sig =
      let doc = "updates the `" ++ memberName ++ "` property of a `" ++ show p ++ "` using a function `f`."
          binderT = sig
          binderP = SymPath (TC.getFullPath candidate) ("update-" ++ (TC.fieldName field))
          temp = TG.generateConcreteFieldTemplate candidate field updateGenerator
       in instanceBinderWithDeps binderP binderT temp doc
templatesForSingleMember _ _ = error "templatesforsinglemember"

-- | Helper function to create the binder for the 'init' template.
binderForInit :: TC.TypeCandidate -> Either TypeError (String, Binder)
binderForInit candidate =
  -- Remove the __dummy field from the members array to ensure we can call the initializer with no arguments.
  -- See the implementation of moduleForDeftype for more details.
  let nodummy = remove ((== "__dummy") . TC.fieldName) (TC.getFields candidate)
      doc = "creates a `" ++ (TC.getName candidate) ++ "`."
      binderP = (SymPath (TC.getFullPath candidate) "init")
      binderT = (FuncTy (concatMap TC.fieldTypes nodummy) (TC.toType candidate) StaticLifetimeTy)
      gen = (initGenerator StackAlloc)
   in if isTypeGeneric (TC.toType candidate)
        then Right (defineTypeParameterizedTemplate (generateGenericTypeTemplate candidate gen) binderP binderT doc)
        else Right (instanceBinder binderP binderT (generateConcreteTypeTemplate candidate gen) doc)

-- | Helper function to create the binder for the 'str' template.
binderForStrOrPrn :: String -> TC.TypeCandidate -> Either TypeError ((String, Binder), [XObj])
binderForStrOrPrn strOrPrn candidate =
  let binderP = SymPath (TC.getFullPath candidate) strOrPrn
      binderT = (FuncTy [RefTy (TC.toType candidate) (VarTy "q")] StringTy StaticLifetimeTy)
      doc = "converts a `" ++ TC.getName candidate ++ "` to a string."
   in if isTypeGeneric (TC.toType candidate)
        then Right $ (defineTypeParameterizedTemplate (TG.generateGenericTypeTemplate candidate strGenerator) binderP binderT doc, [])
        else Right $ instanceBinderWithDeps binderP binderT (TG.generateConcreteTypeTemplate candidate strGenerator) doc

-- | Helper function to create the binder for the 'delete' template.
binderForDelete :: TC.TypeCandidate -> Either TypeError ((String, Binder), [XObj])
binderForDelete candidate =
  let doc = "deletes a `" ++ TC.getName candidate ++ "`. Should usually not be called manually."
      binderP = SymPath (TC.getFullPath candidate) "delete"
      binderT = FuncTy [(TC.toType candidate)] UnitTy StaticLifetimeTy
   in if isTypeGeneric (TC.toType candidate)
        then Right $ (defineTypeParameterizedTemplate (TG.generateGenericTypeTemplate candidate deleteGenerator) binderP binderT doc, [])
        else Right $ instanceBinderWithDeps binderP binderT (TG.generateConcreteTypeTemplate candidate deleteGenerator) doc

-- | Helper function to create the binder for the 'copy' template.
binderForCopy :: TC.TypeCandidate -> Either TypeError ((String, Binder), [XObj])
binderForCopy candidate =
  let doc = "copies a `" ++ TC.getName candidate ++ "`."
      binderP = SymPath (TC.getFullPath candidate) "copy"
      binderT = FuncTy [RefTy (TC.toType candidate) (VarTy "q")] (TC.toType candidate) StaticLifetimeTy
   in if isTypeGeneric (TC.toType candidate)
        then Right $ (defineTypeParameterizedTemplate (TG.generateGenericTypeTemplate candidate copyGenerator) binderP binderT doc, [])
        else Right $ instanceBinderWithDeps binderP binderT (TG.generateConcreteTypeTemplate candidate copyGenerator) doc

--------------------------------------------------------------------------------
-- Template generators
--
-- These functions declaratively specify how C code should be emitted for a
-- type. Binder helpers use these to generate an appropriate template for a
-- bound function name for the type.

-- | getterGenerator returns a template generator for struct property getters.
getterGenerator :: TG.TemplateGenerator TC.TypeField
getterGenerator = TG.mkTemplateGenerator tgen decl body deps
  where
    tgen :: TG.TypeGenerator TC.TypeField
    tgen _ = (FuncTy [RefTy (VarTy "p") (VarTy "q")] (VarTy "t") StaticLifetimeTy)

    decl :: TG.TokenGenerator TC.TypeField
    decl TG.GeneratorArg {instanceT = UnitTy} = toTemplate "void $NAME($(Ref p) p)"
    decl _ = toTemplate "$t $NAME($(Ref p) p)"

    body :: TG.TokenGenerator TC.TypeField
    body TG.GeneratorArg {value = (TC.StructField _ UnitTy)} = toTemplate "$DECL { return; }\n"
    body TG.GeneratorArg {instanceT = (FuncTy _ (RefTy UnitTy _) _)} = toTemplate " $DECL { void* ptr = NULL; return ptr; }\n"
    body TG.GeneratorArg {value = (TC.StructField name ty)} =
      let fixForVoidStarMembers =
            if isFunctionType ty && not (isTypeGeneric ty)
              then "(" ++ tyToCLambdaFix (RefTy ty (VarTy "q")) ++ ")"
              else ""
       in toTemplate ("$DECL { return " ++ fixForVoidStarMembers ++ "(&(p->" ++ (mangle name) ++ ")); }\n")
    body TG.GeneratorArg {} = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeField
    deps = const []

-- | setterGenerator returns a template generator for struct property setters.
setterGenerator :: TG.TemplateGenerator TC.TypeField
setterGenerator = TG.mkTemplateGenerator tgen decl body deps
  where
    tgen :: TG.TypeGenerator TC.TypeField
    tgen _ = (FuncTy [VarTy "p", VarTy "t"] (VarTy "p") StaticLifetimeTy)

    decl :: TG.TokenGenerator TC.TypeField
    decl GeneratorArg {instanceT = (FuncTy [_, UnitTy] _ _)} = toTemplate "$p $NAME($p p)"
    decl _ = toTemplate "$p $NAME($p p, $t newValue)"

    body :: TG.TokenGenerator TC.TypeField
    body GeneratorArg {instanceT = (FuncTy [_, UnitTy] _ _)} = toTemplate "$DECL { return p; }\n"
    body GeneratorArg {tenv, env, instanceT = (FuncTy [_, ty] _ _), value = (TC.StructField name _)} =
      multilineTemplate
        [ "$DECL {",
          memberDeletion tenv env (mangle name, ty),
          "    p." ++ (mangle name) ++ " = newValue;",
          "    return p;",
          "}\n"
        ]
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeField
    deps GeneratorArg {tenv, env, TG.instanceT = (FuncTy [_, ty] _ _)}
      | isManaged tenv env ty = depsOfPolymorphicFunction tenv env [] "delete" (typesDeleterFunctionType ty)
      | isFunctionType ty = [defineFunctionTypeAlias ty]
      | otherwise = []
    deps _ = []

-- | mutatorGenerator returns a template generator for struct property setters (in-place).
mutatorGenerator :: TG.TemplateGenerator TC.TypeField
mutatorGenerator = TG.mkTemplateGenerator tgen decl body deps
  where
    tgen :: TG.TypeGenerator TC.TypeField
    tgen _ = (FuncTy [RefTy (VarTy "p") (VarTy "q"), VarTy "t"] UnitTy StaticLifetimeTy)

    decl :: TG.TokenGenerator TC.TypeField
    decl GeneratorArg {instanceT = (FuncTy [_, UnitTy] _ _)} = toTemplate "void $NAME($p* pRef)"
    decl _ = toTemplate "void $NAME($p* pRef, $t newValue)"

    body :: TG.TokenGenerator TC.TypeField
    -- Execution of the action passed as an argument is handled in Emit.hs.
    body GeneratorArg {instanceT = (FuncTy [_, UnitTy] _ _)} = toTemplate "$DECL { return; }\n"
    body GeneratorArg {tenv, env, instanceT = (FuncTy [_, ty] _ _), value = (TC.StructField name _)} =
      multilineTemplate
        [ "$DECL {",
          memberRefDeletion tenv env (mangle name, ty),
          "    pRef->" ++ mangle name ++ " = newValue;",
          "}\n"
        ]
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeField
    deps GeneratorArg {tenv, env, instanceT = (FuncTy [_, ty] _ _)} =
      if isManaged tenv env ty
        then depsOfPolymorphicFunction tenv env [] "delete" (typesDeleterFunctionType ty)
        else []
    deps _ = []

-- | Returns a template generator for updating struct properties with a function.
updateGenerator :: TG.TemplateGenerator TC.TypeField
updateGenerator = TG.mkTemplateGenerator tgen decl body deps
  where
    tgen :: TG.TypeGenerator TC.TypeField
    tgen GeneratorArg {value = (TC.StructField _ UnitTy)} =
      (FuncTy [VarTy "p", RefTy (FuncTy [] UnitTy (VarTy "fq")) (VarTy "q")] (VarTy "p") StaticLifetimeTy)
    tgen _ = (FuncTy [VarTy "p", RefTy (FuncTy [VarTy "t"] (VarTy "t") (VarTy "fq")) (VarTy "q")] (VarTy "p") StaticLifetimeTy)

    decl :: TG.TokenGenerator TC.TypeField
    decl _ = toTemplate "$p $NAME($p p, Lambda *updater)" -- Lambda used to be (Fn [t] t)
    body :: TG.TokenGenerator TC.TypeField
    body GeneratorArg {value = (TC.StructField _ UnitTy)} =
      toTemplate ("$DECL { " ++ templateCodeForCallingLambda "(*updater)" (FuncTy [] UnitTy (VarTy "fq")) [] ++ "; return p;}\n")
    body GeneratorArg {value = (TC.StructField name _)} =
      multilineTemplate
        [ "$DECL {",
          "    p." ++ mangle name ++ " = " ++ templateCodeForCallingLambda "(*updater)" (FuncTy [VarTy "t"] (VarTy "t") (VarTy "fq")) ["p." ++ mangle name] ++ ";",
          "    return p;",
          "}\n"
        ]
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeField
    deps GeneratorArg {instanceT = (FuncTy [_, RefTy t@(FuncTy fArgTys fRetTy _) _] _ _)} =
      if isTypeGeneric fRetTy
        then []
        else [defineFunctionTypeAlias t, defineFunctionTypeAlias (FuncTy (lambdaEnvTy : fArgTys) fRetTy StaticLifetimeTy)]
    deps _ = []

-- | Returns a template generator for a types initializer function.
initGenerator :: AllocationMode -> TG.TemplateGenerator TC.TypeCandidate
initGenerator alloc = TG.mkTemplateGenerator genT decl body deps
  where
    genT :: TG.TypeGenerator TC.TypeCandidate
    genT GeneratorArg {value} =
      (FuncTy (concatMap TC.fieldTypes (TC.getFields value)) (VarTy "p") StaticLifetimeTy)

    decl :: TG.TokenGenerator TC.TypeCandidate
    decl GeneratorArg {originalT, instanceT = (FuncTy _ concreteT _), value} =
      let mappings = unifySignatures originalT concreteT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
          cFields = remove isUnitT (remove isDummy concreteFields)
       in toTemplate ("$p $NAME(" ++ joinWithComma (map fieldArg cFields) ++ ")")
    decl _ = toTemplate "/* template error! */"

    body :: TG.TokenGenerator TC.TypeCandidate
    body GeneratorArg {originalT, instanceT = (FuncTy _ concreteT _), value} =
      let mappings = unifySignatures originalT concreteT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
       in tokensForInit alloc (show originalT) (remove isUnitT concreteFields)
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeCandidate
    deps GeneratorArg {tenv, env, instanceT = (FuncTy _ concreteT _)} =
      case concretizeType tenv env concreteT of
        Left _ -> []
        Right ok -> ok
    deps _ = []

    tokensForInit :: AllocationMode -> String -> [TC.TypeField] -> [Token]
    -- if this is truly a memberless struct, init it to 0;
    -- This can happen in cases where *all* members of the struct are of type Unit.
    -- Since we do not generate members for Unit types.
    tokensForInit StackAlloc _ [] =
      multilineTemplate
        [ "$DECL {",
          "    $p instance = {};",
          "    return instance;",
          "}"
        ]
    tokensForInit StackAlloc _ fields =
      multilineTemplate
        [ "$DECL {",
          "    $p instance;",
          assignments fields,
          "    return instance;",
          "}"
        ]
    tokensForInit HeapAlloc typeName fields =
      multilineTemplate
        [ "$DECL {",
          "    $p instance = CARP_MALLOC(sizeof(" ++ typeName ++ "));",
          assignments fields,
          "    return instance;",
          "}"
        ]

    assignments :: [TC.TypeField] -> String
    assignments [] = ""
    assignments fields = joinLines $ fmap (memberAssignment alloc) fields

    isDummy field = TC.fieldName field == "__dummy"
    isUnitT (TC.StructField _ UnitTy) = True
    isUnitT _ = False

-- | Generate C code for assigning to a member variable.
-- Needs to know if the instance is a pointer or stack variable.
-- Also handles the special dummy member we add for empty structs to be ANSI C compatible.
memberAssignment :: AllocationMode -> TC.TypeField -> String
memberAssignment allocationMode field =
  case (TC.fieldName field) of
    "__dummy" -> "    instance" ++ sep ++ mangle name ++ " = " ++ "0" ++ ";"
    _ -> "    instance" ++ sep ++ mangle name ++ " = " ++ mangle name ++ ";"
  where
    name = (TC.fieldName field)
    sep = case allocationMode of
      StackAlloc -> "."
      HeapAlloc -> "->"

-- | Creates the C code for an arg to the init function.
-- | i.e. "(deftype A [x Int])" will generate "int x" which
-- | will be used in the init function like this: "A_init(int x)"
fieldArg :: TC.TypeField -> String
fieldArg (TC.StructField name ty) =
  tyToCLambdaFix (templatizeTy ty) ++ " " ++ mangle name
fieldArg _ = ""

---- | Creates the C code for an arg to the init function.
---- | i.e. "(deftype A [x Int])" will generate "int x" which
---- | will be used in the init function like this: "A_init(int x)"
memberArg :: (String, Ty) -> String
memberArg (memberName, memberTy) =
  tyToCLambdaFix (templatizeTy memberTy) ++ " " ++ memberName

-- | If the type is just a type variable; create a template type variable by appending $ in front of it's name
templatizeTy :: Ty -> Ty
templatizeTy (VarTy vt) = VarTy ("$" ++ vt)
templatizeTy (FuncTy argTys retTy ltTy) = FuncTy (map templatizeTy argTys) (templatizeTy retTy) (templatizeTy ltTy)
templatizeTy (StructTy name tys) = StructTy name (map templatizeTy tys)
templatizeTy (RefTy t lt) = RefTy (templatizeTy t) (templatizeTy lt)
templatizeTy (PointerTy t) = PointerTy (templatizeTy t)
templatizeTy t = t

-- | Returns a template generator for a type's str and prn functions.
strGenerator :: TG.TemplateGenerator TC.TypeCandidate
strGenerator = TG.mkTemplateGenerator genT decl body deps
  where
    genT :: TG.TypeGenerator TC.TypeCandidate
    genT GeneratorArg {originalT} =
      FuncTy [RefTy originalT (VarTy "q")] StringTy StaticLifetimeTy

    decl :: TG.TokenGenerator TC.TypeCandidate
    decl GeneratorArg {instanceT = (FuncTy [RefTy structT _] _ _)} =
      toTemplate $ "String $NAME(" ++ tyToCLambdaFix structT ++ " *p)"
    decl _ = toTemplate "/* template error! */"

    body :: TG.TokenGenerator TC.TypeCandidate
    body GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [RefTy structT _] _ _), value} =
      let mappings = unifySignatures originalT structT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
       in tokensForStr tenv env (getStructName structT) concreteFields structT
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeCandidate
    deps arg@GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [RefTy structT _] _ _), value} =
      let mappings = unifySignatures originalT structT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
       in concatMap
            (depsOfPolymorphicFunction tenv env [] "prn" . typesStrFunctionType tenv env)
            (remove isFullyGenericType (concatMap TC.fieldTypes concreteFields))
            ++ [defineFunctionTypeAlias (instanceT arg) | not (isTypeGeneric structT)]
    deps _ = []

    tokensForStr :: TypeEnv -> Env -> String -> [TC.TypeField] -> Ty -> [Token]
    tokensForStr typeEnv env typeName fields concreteStructTy =
      let members = remove ((== "__dummy") . fst) (map fieldToTuple fields)
       in multilineTemplate
            [ "$DECL {",
              "  // convert members to String here:",
              "  String temp = NULL;",
              "  int tempsize = 0;",
              "  (void)tempsize; // that way we remove the occasional unused warning ",
              calculateStructStrSize typeEnv env members concreteStructTy,
              "  String buffer = CARP_MALLOC(size);",
              "  String bufferPtr = buffer;",
              "",
              "  sprintf(bufferPtr, \"(%s \", \"" ++ typeName ++ "\");",
              "  bufferPtr += strlen(\"" ++ typeName ++ "\") + 2;\n",
              joinLines (map (memberPrn typeEnv env) members),
              "  bufferPtr--;",
              "  sprintf(bufferPtr, \")\");",
              "  return buffer;",
              "}"
            ]
    calculateStructStrSize :: TypeEnv -> Env -> [(String, Ty)] -> Ty -> String
    calculateStructStrSize typeEnv env fields s =
      "  int size = snprintf(NULL, 0, \"(%s )\", \"" ++ show s ++ "\");\n"
        ++ unlines (map (memberPrnSize typeEnv env) fields)

-- | Returns a template generator for a type's delete function.
deleteGenerator :: TG.TemplateGenerator TC.TypeCandidate
deleteGenerator = TG.mkTemplateGenerator genT decl body deps
  where
    genT :: TG.TypeGenerator TC.TypeCandidate
    genT _ = FuncTy [VarTy "p"] UnitTy StaticLifetimeTy

    decl :: TG.TokenGenerator TC.TypeCandidate
    decl _ = toTemplate "void $NAME($p p)"

    body :: TG.TokenGenerator TC.TypeCandidate
    body GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [structT] _ _), value} =
      let mappings = unifySignatures originalT structT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
          members = map fieldToTuple concreteFields
       in multilineTemplate
            [ "$DECL {",
              joinLines (map (memberDeletion tenv env) members),
              "}"
            ]
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeCandidate
    deps GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [structT] _ _), value}
      | isTypeGeneric structT = []
      | otherwise =
        let mappings = unifySignatures originalT structT
            concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
         in concatMap
              (depsOfPolymorphicFunction tenv env [] "delete" . typesDeleterFunctionType)
              (filter (isManaged tenv env) (concatMap TC.fieldTypes concreteFields))
    deps _ = []

-- | Returns a template generator for a type's copy function.
copyGenerator :: TG.TemplateGenerator TC.TypeCandidate
copyGenerator = TG.mkTemplateGenerator genT decl body deps
  where
    genT :: TG.TypeGenerator TC.TypeCandidate
    genT _ = FuncTy [RefTy (VarTy "p") (VarTy "q")] (VarTy "p") StaticLifetimeTy

    decl :: TG.TokenGenerator TC.TypeCandidate
    decl _ = toTemplate "$p $NAME($p* pRef)"

    body :: TG.TokenGenerator TC.TypeCandidate
    body GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [RefTy structT _] _ _), value} =
      let mappings = unifySignatures originalT structT
          concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
          members = map fieldToTuple concreteFields
       in tokensForCopy tenv env members
    body _ = toTemplate "/* template error! */"

    deps :: TG.DepenGenerator TC.TypeCandidate
    deps GeneratorArg {tenv, env, originalT, instanceT = (FuncTy [RefTy structT _] _ _), value}
      | isTypeGeneric structT = []
      | otherwise =
        let mappings = unifySignatures originalT structT
            concreteFields = replaceGenericTypeSymbolsOnFields mappings (TC.getFields value)
            members = map fieldToTuple concreteFields
         in concatMap
              (depsOfPolymorphicFunction tenv env [] "copy" . typesCopyFunctionType)
              (filter (isManaged tenv env) (map snd members))
    deps _ = []

--------------------------------------------------------------------------------
-- Utilities

-- | Converts a type field to a tuple of its name and primary type.
-- This is a convenience function for interop with the old tuple based
-- functions for handling type members and it should eventually be deprecated
-- once these functions work on type fields directly.
fieldToTuple :: TC.TypeField -> (String, Ty)
fieldToTuple (TC.StructField name t) = (mangle name, t)
fieldToTuple (TC.SumField name (t : _)) = (mangle name, t) -- note: not actually used.
fieldToTuple (TC.SumField name []) = (mangle name, TypeTy) -- note: not actually used.
